package com.qyxx.platform.common.utils.doc;

import com.qyxx.platform.common.utils.encode.UuidUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * word操作工具类
 * 根据word模板生成多页word文档
 *
 * @author bobgao
 */
public class WordUtils {
    /**
     * poi操作word 解析占位符 并且实现多个word合并 分页下载
     * @throws Exception
     */
    public static File exportWord(List<Map<String, String>> dataList, String templateFilePath) throws Exception {
        //我用的是2007版本以后poi对word进行解析的docx
        //首先定义一个XWPFDocument 集合 这个对象可以进行word 解析 合并 还有下载都离不开这个对象
        List<XWPFDocument> xwpfDocuments = new ArrayList<XWPFDocument>();
        //期次我们先解析word模板 替换其中的占位符
        for (Map<String, String> param : dataList) {
            //进行解析word 并且返回一个XWPFDocument对象 这里面有个坑 就是可能这个${}占位符会分开解析 导致数据没有替换
            //返回一个新的xwpfDocument对象
            XWPFDocument xwpfDocument = generateWord(param, templateFilePath);
            xwpfDocuments.add(xwpfDocument);
        }
        //这样第一步将所有word内容替换之后生成多个   xwpfDocument
        //现在将多个xwpfDocument 进行合并 追加 生成word文件
        //先获取一个模板 以第一个模板为基础进行追加
        File tmpFile = File.createTempFile(UuidUtils.getUUID_16(), ".docx");
        if(!xwpfDocuments.isEmpty()) {
            XWPFDocument xwpfDocument = null;
            for (int i = 0; i < xwpfDocuments.size(); i++) {
                //每次的追加为了避免样式和格式混乱 加上分页符
                //当是只有一条数据的时候 直接输出
                if (i==0) {
                    xwpfDocument = xwpfDocuments.get(0);
                } else {
                    //当存在多条时候
                    xwpfDocument = mergeWord(xwpfDocument, xwpfDocuments.get(i));
                }
            }
            //合并之后返回XWPFDocument对象 写出就可以了
            OutputStream os = new FileOutputStream(tmpFile);
            xwpfDocument.write(os);
            IOUtils.closeQuietly(os);
        }
        return tmpFile;
    }
    /**
     * 替换word占位符的内容 避免了不解析的情况出现
     * @param param
     * @param filePath
     * @return
     */
    public static XWPFDocument generateWordOld(Map<String, String> param, String filePath) {
        XWPFDocument doc = null;
        try {
            OPCPackage pack = POIXMLDocument.openPackage(filePath);
            doc = new XWPFDocument(pack);
            if (param != null && param.size() > 0) {
                //处理段落
                String tempString = "";
                Set<XWPFRun> runSet = new HashSet<>();
                char lastChar = ' ';
                List<XWPFParagraph> paragraphList = doc.getParagraphs();
                if(paragraphList != null && paragraphList.size() > 0){
                    for(XWPFParagraph paragraph:paragraphList){
                        List<XWPFRun> runs = paragraph.getRuns();
                        for (XWPFRun run : runs) {
                            String text = run.getText(0);
                            if(text==null)continue;
                            text = replaceText(text, param);
                            run.setText("",0);
                            run.setText(text,0);
                            for(int i=0;i<text.length();i++){
                                char ch = text.charAt(i);
                                if(ch == '$'){
                                    runSet = new HashSet<>();
                                    runSet.add(run);
                                    tempString = text;
                                }else if(ch == '{'){
                                    if(lastChar == '$'){
                                        if(runSet.contains(run)){

                                        }else{
                                            runSet.add(run);
                                            tempString = tempString+text;
                                        }
                                    }else{
                                        runSet = new HashSet<>();
                                        tempString = "";
                                    }
                                }else if(ch == '}'){

                                    if(tempString!=null&&tempString.indexOf("${")>=0){
                                        if(runSet.contains(run)){

                                        }else{
                                            runSet.add(run);
                                            tempString = tempString+text;
                                        }
                                    }else{
                                        runSet = new HashSet<>();
                                        tempString = "";
                                    }
                                    if(runSet.size()>0){
                                        String replaceText = replaceText(tempString,param);
                                        if(!replaceText.equals(tempString)){
                                            int index = 0;
                                            XWPFRun aRun = null;
                                            for(XWPFRun tempRun:runSet){
                                                tempRun.setText("",0);
                                                if(index==0){
                                                    aRun = tempRun;
                                                }
                                                index++;
                                            }
                                            aRun.setText(replaceText,0);
                                        }
                                        runSet = new HashSet<>();
                                        tempString = "";
                                    }
                                }else{
                                    if(runSet.size()<=0)continue;
                                    if(runSet.contains(run))continue;
                                    runSet.add(run);
                                    tempString = tempString+text;
                                }
                                lastChar = ch;
                            }

                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return doc;
    }

    /**
     * 替换word占位符的内容
     * @param param
     * @param filePath
     * @return
     */
    public static XWPFDocument generateWord(Map<String, String> param, String filePath) {
        XWPFDocument doc = null;
        try {
            doc = new XWPFDocument(POIXMLDocument.openPackage(filePath));
            if (param != null && param.size() > 0){
                //处理段落
                replaceInPara(doc, param);
                //替换表格里面的变量
                replaceInTable(doc, param);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return doc;
    }

    /**
     * 遍历word段落信息
     * @param doc 要替换的文档
     * @param params 参数
     */
    private static void replaceInPara(XWPFDocument doc, Map<String, String> params) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        XWPFParagraph para;
        while (iterator.hasNext()) {
            para = iterator.next();
            replaceInPara(para, params);
        }
    }

    /**
     * 替换段落里面的变量
     * @param para 要替换的段落
     * @param param 参数
     */
    private static void replaceInPara(XWPFParagraph para, Map<String, String> param) {
        List<XWPFRun> runs;
        String tempString = "";
        char lastChar = ' ';
        if (matcher(para.getParagraphText()).find()) {
            runs = para.getRuns();
            Set<XWPFRun> runSet = new HashSet<XWPFRun>();
            for (XWPFRun run : runs) {
                String text = run.getText(0);
                //System.out.println("=======>"+text);
                if(text==null)continue;
                text = replaceText(text,param);
                run.setText("",0);
                run.setText(text,0);

                for(int i=0;i<text.length();i++){
                    char ch = text.charAt(i);
                    if(ch == '$'){
                        runSet = new HashSet<XWPFRun>();
                        runSet.add(run);
                        tempString = text;
                    }else if(ch == '{'){
                        if(lastChar == '$'){
                            if(runSet.contains(run)){

                            }else{
                                runSet.add(run);
                                tempString = tempString+text;
                            }
                        }else{
                            runSet = new HashSet<XWPFRun>();
                            tempString = "";
                        }
                    }else if(ch == '}'){
                        if(tempString!=null&&tempString.indexOf("${")>=0){
                            if(runSet.contains(run)){

                            }else{
                                runSet.add(run);
                                tempString = tempString+text;
                            }
                        }else{
                            runSet = new HashSet<XWPFRun>();
                            tempString = "";
                        }
                        if(runSet.size()>0){
                            String replaceText = replaceText(tempString,param);
                            if(!replaceText.equals(tempString)){
                                int index = 0;
                                XWPFRun aRun = null;
                                for(XWPFRun tempRun:runSet){
                                    tempRun.setText("",0);
                                    if(index==0){
                                        aRun = tempRun;
                                    }
                                    index++;
                                }
                                aRun.setText(replaceText,0);
                            }
                            runSet = new HashSet<XWPFRun>();
                            tempString = "";
                        }
                    }else{
                        if(runSet.size()<=0)continue;
                        if(runSet.contains(run))continue;
                        runSet.add(run);
                        tempString = tempString+text;
                    }
                    lastChar = ch;
                }
            }

            //这种方法会导致占位符被拆开解析，不能识别替换掉
				/*for (int i = 0; i < runs.size(); i++) {
					XWPFRun run = runs.get(i);
					String runText = run.toString();
					System.out.println("====>run:"+runText);
					matcher = this.matcher(runText);
					if (matcher.find()) {
						while ((matcher = this.matcher(runText)).find()) {
							runText = matcher.replaceFirst(String.valueOf(param.get(matcher.group(1))));
						}
						// 直接调用XWPFRun的setText()方法设置文本时，在底层会重新创建一个XWPFRun，把文本附加在当前文本后面，
						// 所以我们不能直接设值，需要先删除当前run,然后再自己手动插入一个新的run。
						para.removeRun(i);
						if(runText.equals("null")){
							runText="";
						}
						para.insertNewRun(i).setText(runText);
					}
				}*/
        }
    }

    /**
     * 替换表格里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {

                    //这种方法会导致表格中的格式丢失
						/*String cellTextString = cell.getText();
	                    for (Entry<String, Object> e : params.entrySet()) {
	                        if (cellTextString.contains("${"+e.getKey()+"}"))
	                            cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());
	                    }
	                    cell.removeParagraph(0);
	                    if(cellTextString.contains("${") && cellTextString.contains("}")){
	                    	cellTextString = "";
	                    }
	                    cell.setText(cellTextString);*/

                    //调用段落替换占位符的方式
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        replaceInPara(para, params);
                    }

                }
            }
        }
    }


    private static String replaceTextOld(String text, Map<String, String> map) {
        if(text != null){
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                key = "${" + key + "}";
                if(text.indexOf(key) != -1){
                    String value = entry.getValue();
                    text = text.replace(key, value);
                }
            }
        }
        return text;
    }

    /**
     * 替换占位符
     * @param text
     * @param map
     * @return
     */
    private static String replaceText(String text, Map<String, String> map) {
        if(text != null){
				/*for (Entry<String, String> entry : map.entrySet()) {
					if (text.contains("${"+entry.getKey()+"}")){
						text = text.replace("${"+entry.getKey()+"}", entry.getValue().toString());
					}
				}*/

            Matcher matcher = matcher(text);
            if (matcher.find()) {
                while ((matcher = matcher(text)).find()) {
                    text = matcher.replaceFirst(String.valueOf(map.get(matcher.group(1))));
                }
                if(text.equals("null")){
                    text="";
                }
            }
        }
        return text;
    }

    /**
     * 正则匹配字符串
     * @param str
     * @return
     */
    private static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 分页合并2个文档
     * @param document
     * @param document2
     * @return
     * @throws Exception
     */
    public static XWPFDocument mergeWord(XWPFDocument document,XWPFDocument document2) throws Exception {
        XWPFDocument src1Document = document ;
        //段后分页符插入
        //src1Document.createParagraph().setPageBreak(true);//该方式会导致后面内容往后一行排列
        src1Document.createParagraph().createRun().addBreak(BreakType.PAGE);//该方式可以避免空行
        CTBody src1Body = src1Document.getDocument().getBody();
        XWPFDocument src2Document = document2;
        CTBody src2Body = src2Document.getDocument().getBody();
        //XWPFParagraph p2 = src2Document.createParagraph();
        XmlOptions optionsOuter = new XmlOptions();
        optionsOuter.setSaveOuter();
        String appendString = src2Body.xmlText(optionsOuter);
        String srcString = src1Body.xmlText();
        String prefix = srcString.substring(0,srcString.indexOf(">")+1);
        String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));
        String sufix = srcString.substring( srcString.lastIndexOf("<") );
        String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
        CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);
        src1Body.set(makeBody);
        return src1Document;
    }

    public static void main(String[] args) throws Exception {
        List<Map<String, String>> dataList = new ArrayList<>();
        Map<String, String> param1 = new  HashMap<String, String>();
        param1.put("型号", "HPP00021SSS");
        param1.put("开票日期", "2018-05-09");
        param1.put("精度", "0.22");
        dataList.add(param1);

        Map<String, String> param2 = new  HashMap<String, String>();
        param2.put("型号", "APP00021SSS");
        param2.put("开票日期", "2019-05-09");
        param2.put("精度", "0.56");
        dataList.add(param2);

        Map<String, String> param3 = new  HashMap<String, String>();
        param3.put("型号", "GOPDDF");
        param3.put("开票日期", "2019-07-09");
        param3.put("精度", "0.7");
        dataList.add(param3);

        File file = exportWord(dataList, "I:/test-word.docx");
        FileUtils.copyFile(file, new File("I:/test-word-export-new.docx"));
    }
}
